﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Text;
using MRS.FrontEnd.BO;

namespace Microsoft.Reputation.Services
{
    class DatabaseAccessDS : IDatabaseAccessDS, IDisposable
    {
        internal class DbAccessState
        {
            public DbAccessState()
            {
            }
        }

        internal class DbAsyncResult<TResult>
            : AsyncResultOwnState<TResult, DbAccessState>
        {
            public DbAsyncResult(DbAccessState ownState, AsyncCallback hostCallback, object hostState)
                : base(ownState, hostCallback, hostState)
            {
            }
        }

        #region Private Members
        private const string DbConnectionStringKey = "PivotDBConnectionString";

        private const string GetOverridesProc = "WebService.prc_GetOverrides";

        private const string SqlLookupQuery = "SELECT ReputationBinary "
            + "FROM WebService.Object (nolock) "
            + "WHERE ObjectTypeId = @p0 "
            + "  AND SHA256 IN ({0})";

        private const string SQLParam = "@p{0}";
        #endregion // Private Members


        public DatabaseAccessDS()
        {
        }

        public void Dispose()
        {
        }

        #region IDatabaseAccessDS Members

        /// <summary>
        /// Query the Business Object Layer for the Unpacked objects that match the ObjectIdentifiers requested
        /// </summary>
        /// <param name="objectIds">The ObjectIdentifiers for the Unpacked objects we want from the BO</param>
        /// <param name="callback">The method that will be called at the completion of the operation that is 
        /// occuring asynchronously</param>
        /// <param name="state">The state object that you would like returned to you when the operation completes</param>
        /// <returns>A reference to the stored context of the execution of this method that is used for restoring 
        /// the operation to be able to finalize the processing</returns>
        public IAsyncResult BeginDBLookupQuery(IList<ObjectIdentifier> objectIds, AsyncCallback callback, object state)
        {
            using (TraceUtility.TraceMethod(Tracing.Source))
            {
                DbAccessState dbState = new DbAccessState();

                DbAsyncResult<List<UnpackedObject>> asyncResult = new DbAsyncResult<List<UnpackedObject>>(
                    dbState,
                    callback,
                    state);

                Exception exception = null;
                List<UnpackedObject> results = null;

                try
                {
                    Stopwatch stopwatch = new Stopwatch();
                    stopwatch.Start();

                    results = UnpackedObject.GetUnpackedObjects(objectIds);

                    stopwatch.Stop();
                    PerfCounters.LookupDBLatency(stopwatch.ElapsedTicks);

                    // Set the perf counters for knowns and unknowns so that 
                    // we can get metrics about our query patterns then feed those
                    // back into test for better performance runs.
                    PerfCounters.KnownResponse(results.Count);
                    PerfCounters.UnknownResponse(objectIds.Count - results.Count);
                    CommonEventLogger.LogDatabaseActionSuccess();
                }
                catch (ApplicationException appEx)
                {
                    CommonEventLogger.LogDatabaseActionFailure(appEx.Message);
                    TraceUtility.TraceCatchException(Tracing.Source, appEx);
                    exception = new LoggedException(appEx);
                }
                catch (Exception ex)
                {
                    TraceUtility.TraceCatchException(Tracing.Source, ex);
                    exception = ex;
                }
                finally
                {
                    if (asyncResult != null)
                    {
                        if (exception == null)
                        {
                            asyncResult.SetComplete(results, true);
                        }
                        else
                        {
                            asyncResult.SetComplete(exception, true);
                        }
                    }
                }

                return asyncResult;
            }
        }

        /// <summary>
        /// Call to complete the asynchronous operation to the layer below.
        /// Should be called by the callback function that is passed to BeginDBLookupQuery
        /// </summary>
        /// <param name="asyncResult">The context object needed to restore the state of 
        /// the call.  This object will store the results of the completion of the BeginDBLookupQuery</param>
        /// <returns>The UnpackedObjects found by calling the BO layer</returns>
        public List<UnpackedObject> EndDBLookupQuery(IAsyncResult asyncResult)
        {
            using (TraceUtility.TraceMethod(Tracing.Source))
            {
                if (asyncResult == null)
                {
                    throw new ArgumentNullException("asyncResult");
                }

                DbAsyncResult<List<UnpackedObject>> ar = asyncResult as DbAsyncResult<List<UnpackedObject>>;

                List<UnpackedObject> results = ar.EndInvoke();

                return results;
            }
        }

        /// <summary>
        /// Syncronous call to the BO layer to get any overrides from the database
        /// </summary>
        /// <param name="latestOverrideStamp">The last override stamp that was gotten from the database</param>
        /// <returns>Override objects to send back to calling clients</returns>
        public List<Override> GetOverrides(long latestOverrideStamp)
        {
            using (TraceUtility.TraceMethod(Tracing.Source))
            {
                List<Override> results = new List<Override>();

                try
                {
                    Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
                    stopwatch.Start();

                    results = UnpackedObject.GetLatestOverride((int)latestOverrideStamp);

                    stopwatch.Stop();

                    PerfCounters.OverridesDBLatency(stopwatch.ElapsedTicks);
                }
                catch (SqlException sqlEx)
                {
                    CommonEventLogger.LogDatabaseActionFailure(sqlEx.Message);
                    TraceUtility.TraceCatchException(Tracing.Source, sqlEx);
                    throw new LoggedException(sqlEx);
                }
                return results;
            }
        }

        #endregion
    }
}
